package cn.siat.gene.service;

import cn.hutool.core.date.DateUnit;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.io.FileUtil;
import cn.hutool.core.io.IoUtil;
import cn.hutool.core.util.IdUtil;
import cn.siat.common.core.exception.ServiceException;
import cn.siat.common.core.utils.FileUploadUtils;
import cn.siat.common.core.web.domain.AjaxResult;
import cn.siat.gene.config.MinioHelper;
import cn.siat.gene.domain.MinioFile;
import cn.siat.gene.mapper.MinioFileMapper;
import cn.siat.gene.model.MinioChunk;
import cn.siat.gene.model.MultipartUploadCreate;
import cn.siat.system.api.domain.SysFile;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import io.minio.*;
import io.minio.errors.*;
import io.minio.http.Method;
import io.minio.messages.Part;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.core.io.InputStreamResource;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;
import cn.siat.gene.config.MinioProperties;

import java.io.*;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.util.*;

/**
 * Minio 文件存储
 * 
 * @author siat
 */
@Service
@Slf4j
@RequiredArgsConstructor
public class MinioFileService
{
    private final MinioProperties minioProperties;

    private final MinioClient client;
    private final MinioHelper minioHelper;
    private final MinioFileMapper minioFileMapper;
    private final String sequenceObjectPath = "sequence/";
    /**
     * 小文件上传
     *
     * @param file 上传的文件
     * @return 访问地址
     * @throws Exception
     */
    public MinioFile uploadFile(MultipartFile file,String md5) throws Exception
    {
        MinioFile minioFile = minioFileMapper.getByMd5(md5);
        if(minioFile!=null && Integer.valueOf(1).equals(minioFile.getIsComplete())){
            return minioFile;
        }
        MinioFile response = MinioFile.builder()
                .fileName(IdUtil.fastSimpleUUID() + "-"+file.getOriginalFilename())
                .md5(md5)
                .chunks(new ArrayList<>())
                .size(file.getSize())
                .chunkCount(1)
                .isComplete(1)
                .build();
        String objectName = getObjectName(response.getFileName());
        PutObjectArgs args = PutObjectArgs.builder()
                .bucket(minioProperties.getBucketName())
                .object(objectName)
                .stream(file.getInputStream(), file.getSize(), -1)
                .contentType(file.getContentType())
                .build();
        client.putObject(args);
        if(minioFile!=null){
            response.setId(minioFile.getId());
            minioFileMapper.updateById(response);
        }else{
            minioFileMapper.insert(response);
        }
        return response;
    }

    public AjaxResult sliceComplete(Integer id,Integer completeChunkNumber){
        MinioFile oldMinioFile = minioFileMapper.selectById(id);
        for (MinioChunk chunk : oldMinioFile.getChunks()) {
            if(chunk.getChunkNumber().equals(completeChunkNumber)){
                chunk.setIsComplete(1);
                chunk.setUploadUrl("");
                break;
            }
        }
        MinioFile updateObj = new MinioFile();
        updateObj.setId(oldMinioFile.getId());
        updateObj.setChunks(oldMinioFile.getChunks());
        minioFileMapper.updateById(updateObj);
        return AjaxResult.success();
    }
    /**
     * 创建切片上传
     * @param createRequestList
     * @return
     */
    public AjaxResult createMultipartUpload(List<MinioFile> createRequestList) {
        List<MinioFile> result = new ArrayList<>();
        for (MinioFile createRequest : createRequestList) {
            String fileName = IdUtil.simpleUUID()+"-"+createRequest.getFileName();
            String objectName= this.getObjectName(fileName);
            MinioFile minioFile = minioFileMapper.getByMd5(createRequest.getMd5());
            MinioFile response = MinioFile.builder()
                    .fileName(fileName)
                    .md5(createRequest.getMd5())
                    .chunks(new ArrayList<>())
                    .size(createRequest.getSize())
                    .chunkCount(createRequest.getChunkCount())
                    .build();
            if(minioFile!=null){
                if(createRequest.getChunkCount()==null){
                    fileName = minioFile.getFileName();
                    objectName= this.getObjectName(fileName);
                    response.setSize(minioFile.getSize());
                    response.setChunkCount(minioFile.getChunkCount());
                    response.setFileName(fileName);
                    createRequest.setSize(minioFile.getSize());
                    createRequest.setChunkCount(minioFile.getChunkCount());
                }
                // 存在就不在传了
                if(Integer.valueOf(1).equals(minioFile.getIsComplete())){
                    result.add(minioFile);
                    continue;
                }else{
                    boolean f = false;
                    // 存在且没有超时的情况下,继续传,
                    if(Math.abs(DateUtil.between(minioFile.getUploadTime(),new Date(), DateUnit.HOUR))<24){
                        for (MinioChunk chunk : minioFile.getChunks()) {
                            if(!Integer.valueOf(1).equals(chunk.getIsComplete())){
                                result.add(minioFile);
                                f = true;
                                break;
                            }
                        }
                        if(f){
                            continue;
                        }
                        // 没有待上传的,状态还没有合并,则重新上传
                    }
                    response.setId(minioFile.getId());
                }
            }

            final MultipartUploadCreate uploadCreate = MultipartUploadCreate.builder()
                    .bucketName(minioProperties.getBucketName())
                    .objectName(objectName)
                    .build();
            final CreateMultipartUploadResponse uploadId = minioHelper.uploadId(uploadCreate);
            uploadCreate.setUploadId(uploadId.result().uploadId());
            response.setUploadId(uploadCreate.getUploadId());
            Map<String, String> reqParams = new HashMap<>();
            reqParams.put("uploadId", uploadId.result().uploadId());
            for (int i = 1; i <= createRequest.getChunkCount(); i++) {
                reqParams.put("partNumber", String.valueOf(i));
                String presignedObjectUrl = minioHelper.getPresignedObjectUrl(minioProperties.getBucketName(),
                        objectName, reqParams);
                MinioChunk item = MinioChunk.builder()
                        .uploadUrl(presignedObjectUrl)
                        .chunkNumber(i)
                        .isComplete(0)
                        .build();
                response.getChunks().add(item);
            }
            response.setUploadTime(new Date());
            if(response.getId()==null){
                minioFileMapper.insert(response);
            }else{
                minioFileMapper.updateById(response);
            }
            result.add(response);
        }
        return AjaxResult.success(result);
    }
    /**
     * 切片合并
     * @param uploadRequestList
     */
    public AjaxResult completeMultipartUpload(List<MinioFile> uploadRequestList) {
        List<String> result = new ArrayList<>();
        try {
            for (MinioFile uploadRequest : uploadRequestList) {
                final ListPartsResponse listMultipart = minioHelper.listMultipart(MultipartUploadCreate.builder()
                        .bucketName(minioProperties.getBucketName())
                        .objectName(getObjectName(uploadRequest.getFileName()))
                        .maxParts(uploadRequest.getChunkCount() + 10)
                        .uploadId(uploadRequest.getUploadId())
                        .partNumberMarker(0)
                        .build());
                final ObjectWriteResponse objectWriteResponse = minioHelper.completeMultipartUpload(MultipartUploadCreate.builder()
                        .bucketName(minioProperties.getBucketName())
                        .uploadId(uploadRequest.getUploadId())
                        .objectName(getObjectName(uploadRequest.getFileName()))
                        .parts(listMultipart.result().partList().toArray(new Part[]{}))
                        .build());
                result.add(uploadRequest.getFileName());
                MinioFile updateObj = new MinioFile();
                updateObj.setId(uploadRequest.getId());
                updateObj.setIsComplete(1);
                minioFileMapper.updateById(updateObj);
            }
        } catch (Exception e) {
            log.error("合并切片失败", e);
            throw new ServiceException("合并切片失败");
        }
        return AjaxResult.success(result);
    }


    public void remove(String fileName) {
        if (StringUtils.isBlank(fileName)) return;
        log.info("删除文件开始, fileName: [{}]",fileName);
        try {
            minioHelper.removeFile(fileName);
        } catch (Exception e) {
            log.error("删除文件失败", e);
        }
        log.info("删除文件结束, fileName: [{}]",fileName);
    }
    private String getObjectName(String fileName){
        return sequenceObjectPath+fileName;
    }
    public List<String> downLoadToServer(List<Long> idList,String folderPath){
        List<String> result = new ArrayList<>();
        try {
            for (Long id : idList) {
                MinioFile minioFile = minioFileMapper.selectById(id);
                String objectName = getObjectName(minioFile.getFileName());
                try {
                    client.statObject(StatObjectArgs.builder().bucket(minioProperties.getBucketName()).object(objectName).build());
                }catch (Exception e){
                    // 不存在这个文件,修改表中已完成状态
                    minioFileMapper.updateById(MinioFile.builder().id(id).isComplete(0).build());
                    throw new ServiceException("minio下载出错");
                }
                InputStream in = client.getObject(GetObjectArgs.builder().bucket(minioProperties.getBucketName())
                        .object(objectName).build());
                File f = new File(folderPath);
                f.mkdirs();
                OutputStream os = new FileOutputStream(f.getPath()+"/"+minioFile.getFileName());
                IoUtil.copy(in,os);
                in.close();
                os.close();
                result.add(minioFile.getFileName());
            }
        }catch (Exception e){
            log.error("从minio下载至服务器出错",e);
            throw new ServiceException("minio下载出错");
        }
        return result;
    }
    public String getDownLoadUrl(String md5){
        MinioFile minioFile = minioFileMapper.getByMd5(md5);
        if(minioFile==null){
            throw new ServiceException("未查到文件");
        }
        if(!Integer.valueOf(1).equals(minioFile.getIsComplete())){
            throw new ServiceException("该文件未上传完毕");
        }
        String objectName = getObjectName(minioFile.getFileName());
        GetPresignedObjectUrlArgs obj = GetPresignedObjectUrlArgs.builder()
                .method(Method.GET)
                .bucket(minioProperties.getBucketName())
                .object(objectName)
                .build();
        try{
            return client.getPresignedObjectUrl(obj);
        }catch (Exception e){
            throw new ServiceException("获取minio下载失败");
        }
    }

}
